package repository

import (
	"context"
	"net/http"
	"server/components/errors"
	"server/components/httputils"
	"server/components/models/ent"
	"server/components/models/ent/region"
	"server/components/models/ent/regiontype"
	"server/components/models/ent/schema"
	"strings"
)

func GetRegionTypeByID(ctx context.Context, id int64) (*RegionType, errors.RequestError) {
	regionType, err := EntClient().RegionType.Get(ctx, id)
	if ent.IsNotFound(err) {
		return nil, errors.New("Регион не найден", http.StatusNotFound)
	} else if err != nil {
		return nil, errors.MsgWrap("Проблемы с запросом", err, http.StatusInternalServerError)
	}
	return &RegionType{
		ID:   regionType.ID,
		Type: regionType.Type,
	}, nil
}

func GetRegionByID(ctx context.Context, id int64) (*Region, errors.RequestError) {
	region, err := EntClient().Region.Query().
		Where(region.ID(id)).
		WithAccountId().
		WithRegionType().Only(ctx)
	if ent.IsNotFound(err) {
		return nil, errors.New("Регион не найден", http.StatusNotFound)
	} else if err != nil {
		return nil, errors.MsgWrap("Проблемы с запросом", err, http.StatusInternalServerError)
	}
	if region.Edges.RegionType == nil {
		region.Edges.RegionType = &ent.RegionType{ID: -1}
	}
	tmp := &Region{
		ID:           region.ID,
		Name:         region.Name,
		ParentRegion: region.ParentRegion,
		AccountId:    region.Edges.AccountId.ID,
		RegionType:   region.Edges.RegionType.ID,
		Latitude1:    region.Latitude1,
		Longitude1:   region.Longitude1,
		Latitude2:    region.Latitude2,
		Longitude2:   region.Longitude2,
		OsmData: &OsmData{
			MapLink:          region.OsmData.MapLink,
			BoundingBox:      convertFVecToSVec(region.OsmData.BoundingBox),
			PointsOfInterest: region.OsmData.PointsOfInterest,
		},
	}
	return tmp, nil
}

func AddRegionType(ctx context.Context, req AddRegionTypeRequest) (*RegionType, errors.RequestError) {
	if req.Type == nil || *req.Type == "" {
		return nil, errors.New("Некорректные данные", http.StatusBadRequest)
	}
	rt, err := EntClient().RegionType.Create().SetType(*req.Type).Save(ctx)
	if ent.IsConstraintError(err) {
		return nil, errors.New("Такой тип региона уже существует", http.StatusConflict)
	} else if err != nil {
		return nil, errors.MsgWrap("Проблемы с запросом", err, http.StatusInternalServerError)
	}
	return &RegionType{
		ID:   rt.ID,
		Type: rt.Type,
	}, nil
}

func AddRegion(ctx context.Context, req AddRegionRequest) (*Region, errors.RequestError) {
	if req.Name == nil {
		return nil, errors.New("Некорректные данные", http.StatusBadRequest)
	}
	name := strings.TrimSpace(*req.Name)
	if name == "" {
		return nil, errors.New("Некорректные данные, имя пустое", http.StatusBadRequest)
	}
	userID := ctx.Value(httputils.UserIDCtxKey).(int)
	create := EntClient().Region.Create().
		SetAccountIdID(userID).
		SetLatitude1(req.Latitude1).
		SetLongitude1(req.Longitude1).
		SetLatitude2(req.Latitude2).
		SetLongitude2(req.Longitude2).
		SetName(name)
	if req.OsmData != nil {
		b, err := convertSFloatVec(req.OsmData.BoundingBox)
		if err != nil {
			return nil, err
		}
		osm := schema.OsmData{
			MapLink:     req.OsmData.MapLink,
			BoundingBox: b,
		}
		if req.OsmData.IsIncludePOI() {
			osm.PointsOfInterest = req.OsmData.PointsOfInterest
		}
		create = create.SetOsmData(osm)
	}
	if req.ParentRegion != nil {
		create = create.SetParentRegion(*req.ParentRegion)
	}
	if req.RegionType != nil {
		create = create.SetRegionTypeID(*req.RegionType)
	}
	region, err := create.Save(ctx)
	if ent.IsConstraintError(err) {
		return nil, errors.New("Регион с такими координатами уже существует", http.StatusConflict)
	} else if err != nil {
		return nil, errors.MsgWrap("Проблемы с запросом", err, http.StatusInternalServerError)
	}
	return GetRegionByID(ctx, region.ID)
}

func UpdateRegion(ctx context.Context, id int64, req UpdateRegionRequest) (*Region, errors.RequestError) {
	update := EntClient().Region.UpdateOneID(id)
	if req.OsmData != nil {
		b, err := convertSFloatVec(req.OsmData.BoundingBox)
		if err != nil {
			return nil, err
		}
		osm := schema.OsmData{
			MapLink:     req.OsmData.MapLink,
			BoundingBox: b,
		}
		if req.OsmData.IsIncludePOI() {
			osm.PointsOfInterest = req.OsmData.PointsOfInterest
		}
		update = update.SetOsmData(osm)
	}
	if req.Latitude1 != nil {
		update = update.SetLatitude1(*req.Latitude1)
	}
	if req.Longitude1 != nil {
		update = update.SetLongitude1(*req.Longitude1)
	}
	if req.Latitude2 != nil {
		update = update.SetLatitude2(*req.Latitude2)
	}
	if req.Longitude2 != nil {
		update = update.SetLongitude2(*req.Longitude2)
	}
	if req.Name != nil {
		name := strings.TrimSpace(*req.Name)
		if name == "" {
			return nil, errors.New("Некорректные данные, имя пустое", http.StatusBadRequest)
		}
		update = update.SetName(name)
	}
	if req.ParentRegion != nil {
		update = update.SetParentRegion(*req.ParentRegion)
	}
	if req.RegionType != nil {
		update = update.SetRegionTypeID(*req.RegionType)
	}
	_, err := update.Save(ctx)
	if ent.IsNotFound(err) {
		return nil, errors.New("Регион не найден", http.StatusNotFound)
	} else if ent.IsConstraintError(err) {
		return nil, errors.New("Регион с такими координатами уже существует", http.StatusConflict)
	} else if err != nil {
		return nil, errors.MsgWrap("Проблемы с запросом", err, http.StatusInternalServerError)
	}
	return GetRegionByID(ctx, id)
}

func UpdateRegionType(ctx context.Context, id int64, req UpdateRegionTypeRequest) (*RegionType, errors.RequestError) {
	update := EntClient().RegionType.UpdateOneID(id)
	if req.Type != nil {
		update = update.SetType(*req.Type)
	}
	_, err := update.Save(ctx)
	if ent.IsNotFound(err) {
		return nil, errors.New("Тип региона не найден", http.StatusNotFound)
	} else if ent.IsConstraintError(err) {
		return nil, errors.New("Такой тип региона уже существует", http.StatusConflict)
	} else if err != nil {
		return nil, errors.MsgWrap("Проблемы с запросом", err, http.StatusInternalServerError)
	}
	return GetRegionTypeByID(ctx, id)
}

func DeleteRegion(ctx context.Context, id int64) (*httputils.EmptyResponse, errors.RequestError) {
	tx, err := EntClient().Tx(ctx)
	if err != nil {
		return nil, errors.New("Ошибка удаления", http.StatusInternalServerError)
	}
	defer tx.Rollback()
	reg, err := tx.Region.Get(ctx, id)
	if ent.IsNotFound(err) {
		return nil, errors.New("Регион не найден", http.StatusNotFound)
	} else if err != nil {
		return nil, errors.New("Ошибка удаления", http.StatusInternalServerError)
	}
	if ok, _ := tx.Region.Query().Where(
		region.ParentRegion(reg.Name),
	).Exist(ctx); ok {
		return nil, errors.New("Регион является родительским для других регионов", http.StatusBadRequest)
	}
	err = tx.Region.DeleteOneID(id).Exec(ctx)
	if ent.IsNotFound(err) {
		return nil, errors.New("Регион не найден", http.StatusNotFound)
	} else if err != nil {
		return nil, errors.New("Ошибка удаления", http.StatusInternalServerError)
	}
	if err := tx.Commit(); err != nil {
		return nil, errors.New("Ошибка удаления", http.StatusInternalServerError)
	}
	return &httputils.EmptyResponse{}, nil
}

func DeleteRegionType(ctx context.Context, id int64) (*httputils.EmptyResponse, errors.RequestError) {
	tx, err := EntClient().Tx(ctx)
	if err != nil {
		return nil, errors.New("Ошибка удаления", http.StatusInternalServerError)
	}
	defer tx.Rollback()
	if ok, _ := tx.Region.Query().Where(
		region.HasRegionTypeWith(regiontype.ID(id)),
	).Exist(ctx); ok {
		return nil, errors.New("Есть регионы с данным типом", http.StatusBadRequest)
	}
	err = tx.RegionType.DeleteOneID(id).Exec(ctx)
	if ent.IsNotFound(err) {
		return nil, errors.New("Регион не найден", http.StatusNotFound)
	} else if err != nil {
		return nil, errors.New("Ошибка удаления", http.StatusInternalServerError)
	}
	if err := tx.Commit(); err != nil {
		return nil, errors.New("Ошибка удаления", http.StatusInternalServerError)
	}
	return &httputils.EmptyResponse{}, nil
}
